/***************************************************************************
 *  WlanSavvy
 *  Copyright  2007  Deepak Manoharan
 *  Email deepakm@gmail.com
 *  http://wlansavvy.googlecode.com/
 ****************************************************************************/

/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/**
 * Some of the routines are ripped of or heavily inspired by
 * Wavemon
 * Copyright (c) 2001-2002 Jan Morgenstern <jan@jm-music.de>
 */

#include "wl_if.h"

static GSList * if_list = NULL;
static gchar current_if[IFNAMSIZ];
static char *modes[] = {"Auto","Ad-hoc","Managed","Master","Repeater","Secondary"};
static char *encval[] = {"Unknown","Off","Restricted","Open"};
static wl_info_t wlinfo;


void init_wlif()
{
	memset(&wlinfo,0,sizeof(wl_info_t));
}

gfloat dbm2mw(gfloat in)
{
    return((gfloat)pow(10.0, in / 10.0));
}

gfloat mw2dbm(gfloat in)
{
    return((gfloat)pow(10.0, in / 10.0));
}

gint load_wlan_interfaces()
{
	FILE * fd;
	char if_name[IFNAMSIZ+1];
	if(!(fd = fopen(PROC_NET_WIRELESS,"r"))) return 0;
	
	while(fgets(if_name,IFNAMSIZ+1,fd)){
		if(!strchr(if_name,':'))
			continue;
		char *tmp = if_name + strspn(if_name," "); //Skip leading whitespaces
		tmp[strcspn(tmp,":")] = '\0';
		if_list = g_slist_append(if_list,tmp);
		//TODO: Move it somewhere appropriate once config panel is implemented
		set_lbl_interface(tmp);
		strcpy(current_if,tmp);
	}
	
	fclose(fd);
	return g_slist_length(if_list);
}


gint if_getstat(gchar *ifname, wl_info_t *wlinfo)
{
    FILE    *fd;
    char    tmp[0x100];
    unsigned long d;
    char    *lp;

    if ((fd = fopen("/proc/net/dev", "r")) < 0) {
        g_print("fatal error: cannot open /proc/net/dev\n");
		return -1;
    }

    while (fgets(tmp, 0x100, fd)) {
        lp = tmp + strspn(tmp, " ");
        if (!strncmp(lp, ifname, strlen(ifname))) {
            lp += strlen(ifname) + 1;
            lp += strspn(lp, " ");

            sscanf(lp, "%llu %llu %lu %lu %lu %lu %lu %lu %llu %llu",
                &wlinfo->rx_bytes, &wlinfo->rx_packets, &d, &d, &d, &d, &d, &d,
                &wlinfo->tx_bytes, &wlinfo->tx_packets);
        }
    }

    fclose(fd);
	return 0;
}


gint get_if_statistics(gchar * if_name, wl_info_t *wlinfo)
{
	struct iwreq req;
	int sockfd;
	
	if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){
		/*TODO: Push into error list*/
		printf("Socket open failed\n");
		return -1;
	}
	
	/*Wireless information*/
	strncpy(req.ifr_name, if_name, IFNAMSIZ);

	/* Stats */
	req.u.data.pointer = &(wlinfo->stats);
	req.u.data.length = sizeof(iwstats);
	req.u.data.flags = 1;     /* Clear updated flag */
	if(ioctl(sockfd,SIOCGIWSTATS, &req) >= 0){
		wlinfo->has_stats =1;
	}

	close(sockfd);
	return if_getstat(if_name,wlinfo);
}


gint get_if_info(gchar * if_name, wl_info_t * wlinfo)
{
	struct iwreq req;
	struct ifreq ifreq;
	int sockfd;
	
	if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){
		/*TODO: Push into error list*/
		printf("Socket open failed\n");
		return -1;
	}
	
	/*Interface information*/
    strncpy(ifreq.ifr_name, if_name, IFNAMSIZ);
    if (ioctl(sockfd, SIOCGIFADDR, &ifreq) >= 0)
        memcpy(&wlinfo->addr, &ifreq.ifr_addr.sa_data[2], 4);
    if (ioctl(sockfd, SIOCGIFHWADDR, &ifreq) >= 0)
        memcpy(&wlinfo->hwaddr, &ifreq.ifr_hwaddr.sa_data, 6);
    if (ioctl(sockfd, SIOCGIFNETMASK, &ifreq) >= 0)
        memcpy(&wlinfo->mask, &ifreq.ifr_netmask.sa_data[2], 4); 

	/*Wireless information*/
	/* NAME */
	strncpy(req.ifr_name, if_name, IFNAMSIZ);
    if (ioctl(sockfd, SIOCGIWNAME, &req) < 0) {
		printf("cannot open device '%s'", req.u.name);
		/*If u cant open.. means u dont have wireless extensions supported*/
		return -1;
	}	
	strncpy(wlinfo->name, req.u.name, IFNAMSIZ);

	/* ESSID */
    req.u.essid.pointer = (caddr_t)&(wlinfo->essid);
    req.u.essid.length = IW_ESSID_MAX_SIZE + 1;
    req.u.essid.flags = 0;
    if (ioctl(sockfd, SIOCGIWESSID, &req) >= 0) {
        wlinfo->has_essid = 1;
        wlinfo->essid_on = req.u.essid.flags;
    }
	
	/* AP Address */
	if (ioctl(sockfd, SIOCGIWAP, &req) >= 0) {
		wlinfo->has_ap_addr = 1;
		memcpy(&wlinfo->ap_addr, &req.u.ap_addr, sizeof(struct sockaddr));
	}

	/* Frequency */
	if (ioctl(sockfd, SIOCGIWFREQ, &req) >= 0) {
        wlinfo->has_freq = 1;
        if (req.u.freq.e)
            wlinfo->freq = (req.u.freq.m * pow(10, req.u.freq.e)) / 1000000000;
        else wlinfo->freq = req.u.freq.m;
    }

	/* Bitrate */
	if (ioctl(sockfd, SIOCGIWRATE, &req) >= 0) {
		wlinfo->has_bitrate = 1;
		wlinfo->bitrate = req.u.bitrate.value;
	}

	/* Sensitivity */
	if (ioctl(sockfd, SIOCGIWSENS, &req) >= 0) {
		wlinfo->has_sensitivity = 1;
		wlinfo->sensitivity = req.u.sens.value;
	}

	/* Mode */
	if (ioctl(sockfd, SIOCGIWMODE, &req) >= 0) {
		wlinfo->has_mode = 1;
		wlinfo->mode = req.u.mode;
	}

	/* TX Power*/
    if (ioctl(sockfd, SIOCGIWTXPOW, &req) >= 0) {
        wlinfo->has_txpower = 1;
        if (req.u.txpower.flags == IW_TXPOW_DBM) {
            wlinfo->txpower_dbm = req.u.txpower.value;
            wlinfo->txpower_mw = dbm2mw(req.u.txpower.value);
        } else {
            wlinfo->txpower_mw = req.u.txpower.value;
            wlinfo->txpower_dbm = mw2dbm(req.u.txpower.value);
        }
    }
	
	/* RTS */
    if (ioctl(sockfd, SIOCGIWRTS, &req) >= 0) {
        wlinfo->has_rts = 1;
        wlinfo->rts_val = req.u.rts.value;
        wlinfo->rts_off = req.u.rts.disabled;
    }
    
	/* FRAG */
    if (ioctl(sockfd, SIOCGIWFRAG, &req) >= 0) {
        wlinfo->has_frag = 1;
        wlinfo->frag_val = req.u.frag.value;
        wlinfo->frag_off = req.u.frag.disabled;
    }

	/* Range */
	req.u.data.pointer = &(wlinfo->range);
	req.u.data.length = sizeof(iwrange);
	req.u.data.flags = 0;     /* Clear updated flag */
	if(ioctl(sockfd,SIOCGIWRANGE, &req) >= 0){
		wlinfo->has_range =1;
	}

	/* Encryption */
	req.u.data.pointer = &(wlinfo->key);
    req.u.data.length = IW_ENCODING_TOKEN_MAX;
    req.u.data.flags = 0;
    if (ioctl(sockfd, SIOCGIWENCODE, &req) >= 0) {
        wlinfo->has_encryption = 1; 
        wlinfo->enc_val = 0;
        if (req.u.data.flags & IW_ENCODE_DISABLED)
            wlinfo->enc_val = 1;
        if (req.u.data.flags & IW_ENCODE_RESTRICTED)
            wlinfo->enc_val = 2;
        if (req.u.data.flags & IW_ENCODE_OPEN)
            wlinfo->enc_val = 3;
    }           

	close(sockfd);
	return 0;
}

void refresh_graph()
{
	if(wlinfo.has_stats){
		gint signal, noise, snr;
		signal =  wlinfo.stats.qual.level-0x100;
		noise =  wlinfo.stats.qual.noise-0x100;
		snr = signal-noise;
		insert_graph_points( 110+signal, 110+noise, snr);
	}
}


void refresh_if_statistics()
{
	get_if_statistics(current_if,&wlinfo);
	//TODO: Move stats alone for frequent updates
	if(wlinfo.has_stats){
		gchar temp[200];
		gint signal;
		gint noise;
		gint snr;

		//Link
		sprintf(temp,"%d/%d", wlinfo.stats.qual.qual,wlinfo.range.max_qual.qual);
		set_pgbar_link_text(temp);
		set_pgbar_link_fraction((gdouble)wlinfo.stats.qual.qual/(gdouble)wlinfo.range.max_qual.qual);

		//Signal
		signal =  wlinfo.stats.qual.level-0x100;
		sprintf(temp,"%d dBm (%.2f uW)", signal,dbm2mw(signal)*1000);
		set_pgbar_signal_text(temp);
		set_pgbar_signal_fraction((gdouble)(10-signal)/(gdouble)(130));

		//Noise
		noise =  wlinfo.stats.qual.noise-0x100;
		sprintf(temp,"%d dBm (%.2f uW)", noise,dbm2mw(noise)*1000);
		set_pgbar_noise_text(temp);
		set_pgbar_noise_fraction( (gdouble)1 - ( (gdouble)(10-noise)/(gdouble)(130)) );

		//SNR
		snr = signal-noise;
		sprintf(temp,"%d dBm", snr);
		set_pgbar_snr_text(temp);
		set_pgbar_snr_fraction( (gdouble)snr / (gdouble)110 );

		//TX
		sprintf(temp,"%llu (%llu)",wlinfo.tx_packets, wlinfo.tx_bytes);
		set_lbl_tx(temp);

		//RX
		sprintf(temp,"%llu (%llu)",wlinfo.rx_packets, wlinfo.rx_bytes);
		set_lbl_rx(temp);

		//RX Invalid
		sprintf(temp,"N:%d C:%d F:%d",wlinfo.stats.discard.nwid,
									wlinfo.stats.discard.code,	
									wlinfo.stats.discard.fragment);
		set_lbl_rxinvalid(temp);

		//Missed beacon
		sprintf(temp,"%d",wlinfo.stats.miss.beacon);
		set_lbl_beacon(temp);

		//TX retries
		sprintf(temp,"%d",wlinfo.stats.discard.retries);
		set_lbl_txretries(temp);

		//Invalid misc
		sprintf(temp,"%d",wlinfo.stats.discard.misc);
		set_lbl_invalidmisc(temp);
	}

}

//TODO: Move it somewhere appropriate once config panel is implemented
void refresh_if_info()
{
	char temp[200];
	get_if_info(current_if,&wlinfo);

	sprintf(temp,"%s (%s)",current_if,wlinfo.name);
	set_lbl_interface(temp);

	set_lbl_essid(wlinfo.essid);

	if(wlinfo.has_ap_addr)
		sprintf(temp,"%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX",
                wlinfo.ap_addr.sa_data[0] & 0xFF,
                wlinfo.ap_addr.sa_data[1] & 0xFF,
                wlinfo.ap_addr.sa_data[2] & 0xFF,
                wlinfo.ap_addr.sa_data[3] & 0xFF,
                wlinfo.ap_addr.sa_data[4] & 0xFF,
                wlinfo.ap_addr.sa_data[5] & 0xFF);
	else sprintf(temp,"Not Available");
	set_lbl_accesspoint(temp);
	
	/*TODO : Include mask bits*/
	sprintf(temp,"%hhu.%hhu.%hhu.%hhu",
                wlinfo.addr[0] & 0xFF,
                wlinfo.addr[1] & 0xFF,
                wlinfo.addr[2] & 0xFF,
                wlinfo.addr[3] & 0xFF);
	set_lbl_ipadrmask(temp);
	
	if(wlinfo.has_freq)
		sprintf(temp,"%.2f GHz",wlinfo.freq);
	else sprintf(temp,"%s","Unknown");
	set_lbl_frequency(temp);

	/*TODO: Set sensitivity with range sensitivity*/
	if(wlinfo.has_sensitivity)
		sprintf(temp,"%ld/%d",wlinfo.sensitivity,wlinfo.range.sensitivity);
	else sprintf(temp,"Unknown");
	set_lbl_sensitivity(temp);
	
	if(wlinfo.has_bitrate)
		sprintf(temp, "%g Mbit/s", (double)wlinfo.bitrate / 1000000);
	else sprintf(temp,"Unknown");
	set_lbl_bitrate(temp);
	
	if(wlinfo.has_mode)
		sprintf(temp, "%s",modes[wlinfo.mode]);
	else sprintf(temp,"Unknown");
	set_lbl_mode(temp);

	if(wlinfo.has_txpower)
		sprintf(temp,"%d dBm (%.2f mW)",wlinfo.txpower_dbm,wlinfo.txpower_mw);
	else sprintf(temp,"Unknown");
	set_lbl_txpower(temp);

	set_lbl_encryption(encval[wlinfo.enc_val]);

	if(wlinfo.has_rts){
		sprintf(temp,"%d bytes",wlinfo.rts_val);
		if(wlinfo.rts_off)
			sprintf(temp,"Off");
	}
	else sprintf(temp,"Unknown");
	set_lbl_rts(temp);

	if(wlinfo.has_frag){
		sprintf(temp,"%d bytes",wlinfo.frag_val);
		if(wlinfo.frag_off)
			sprintf(temp,"Off");
	}
	else sprintf(temp,"Unknown");
	set_lbl_frag(temp);

}


